package LDraw.Support; import java.nio.FloatBuffer; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.Timer; import java.util.TreeSet; import javax.media.opengl.GL2; import Command.LDrawColor; import Command.LDrawColorT; import Command.LDrawColorable; import Common.Box2; import Common.Box3; import Common.Matrix4; import Common.Size2; import Common.Vector2f; import Common.Vector3f; import Common.Vector4f; import LDraw.Files.LDrawFile; import LDraw.Files.LDrawModel; import LDraw.Support.type.LocationModeT; import LDraw.Support.type.ProjectionModeT; import LDraw.Support.type.RotationDrawModeT; import LDraw.Support.type.ViewOrientationT; import Notification.ILDrawSubscriber; import Notification.INotificationMessage; import Notification.NotificationCenter; import Notification.NotificationMessageT; import Renderer.LDrawShaderRenderer; //============================================================================== // //File: LDrawGLRenderer.h // //Purpose: Draws an LDrawFile with OpenGL. // //Modified: 4/17/05 Allen Smith. Creation Date. // //============================================================================== //============================================================================== // //File: LDrawGLRenderer.m // //Purpose: Draws an LDrawFile with OpenGL. // // This class is responsible for all platform-independent logic, // including math and OpenGL operations. It also contains a number // of methods which would be called in response to events; it is // the responsibility of the platform layer to receive and // interpret those events and pass them to us. // // The "event" type methods here take high-level parameters. For // example, we don't check -- or want to know! -- if the option key // is down. The platform layer figures out stuff like that, and // more importantly, figures out what it *means*. The *meaning* is // what the renderer's methods care about. // //Created by Allen Smith on 4/17/05. //Copyright 2005. All rights reserved. //============================================================================== /** * @Class LDrawGLRenderer * * @Purpose This is an abstract base class for all elements of an LDraw * @Represent LDrawGLRenderer.(h, m) of Bricksmith * * @author funface2 * @since 2014-03-17 * */ public class LDrawGLRenderer implements LDrawColorable{ public static final float SIMPLIFICATION_THRESHOLD = 0.3f; // seconds public static final int HANDLE_SIZE = 3; /** * @uml.property name="delegate" * @uml.associationEnd */ LDrawGLRendererDelegate delegate; /** * @uml.property name="scroller" * @uml.associationEnd */ ILDrawGLCameraScroller scroller; /** * @uml.property name="target" */ Object target; /** * @uml.property name="backAction" * @uml.associationEnd */ SelT backAction; /** * @uml.property name="forwardAction" * @uml.associationEnd */ SelT forwardAction; /** * @uml.property name="nudgeAction" * @uml.associationEnd */ SelT nudgeAction; /** * @uml.property name="allowsEditing" */ boolean allowsEditing; /** * @uml.property name="fileBeingDrawn" * @uml.associationEnd */ LDrawDirective fileBeingDrawn; // Should only be an LDrawFile or LDrawModel. // if you want to do anything else, you must // tweak the selection code in LDrawDrawableElement // and here in -mouseUp: to handle such cases. /** * @uml.property name="camera" * @uml.associationEnd */ LDrawGLCamera camera; // Drawing Environment /** * @uml.property name="color" * @uml.associationEnd */ LDrawColor color;// default color to draw parts if none is specified /** * @uml.property name="glBackgroundColor" multiplicity="(0 -1)" * dimension="1" */ float glBackgroundColor[] = new float[4]; /** * @uml.property name="selectionMarquee" * @uml.associationEnd */ Box2 selectionMarquee;// in view coordinates. ZeroBox2 means no marquee. /** * @uml.property name="rotationDrawMode" * @uml.associationEnd */ RotationDrawModeT rotationDrawMode; // // drawing detail while rotating. /** * @uml.property name="viewOrientation" * @uml.associationEnd */ ViewOrientationT viewOrientation;// our orientation /** * @uml.property name="fpsStartTime" */ Date fpsStartTime = Calendar.getInstance().getTime(); /** * @uml.property name="framesSinceStartTime" */ int framesSinceStartTime; // Event Tracking /** * @uml.property name="gridSpacing" */ float gridSpacing; /** * @uml.property name="isGesturing" */ boolean isGesturing; // true if performing a multitouch trackpad gesture. /** * @uml.property name="isTrackingDrag" */ boolean isTrackingDrag; // true if the last mousedown was followed by a // drag, and we're tracking it (drag-and-drop // doesn't count) /** * @uml.property name="isStartingDrag" */ boolean isStartingDrag; // this is the first event in a drag /** * @uml.property name="mouseDownTimer" */ Timer mouseDownTimer; // countdown to beginning drag-and-drop /** * @uml.property name="canBeginDragAndDrop" */ boolean canBeginDragAndDrop; // the next mouse-dragged will initiate a // drag-and-drop. /** * @uml.property name="didPartSelection" */ boolean didPartSelection; // tried part selection during this click /** * @uml.property name="dragEndedInOurDocument" */ boolean dragEndedInOurDocument; // YES if the drag we initiated ended in the // document we display /** * @uml.property name="draggingOffset" * @uml.associationEnd */ Vector3f draggingOffset; // displacement between part 0's position and the // initial click point of the drag /** * @uml.property name="initialDragLocation" * @uml.associationEnd readOnly="true" */ Vector3f initialDragLocation; // point in model where part was positioned at // draggingEntered /** * @uml.property name="nudgeVector" * @uml.associationEnd readOnly="true" */ Vector3f nudgeVector; // direction of nudge action (valid only in // nudgeAction callback) /** * @uml.property name="activeDragHandle" * @uml.associationEnd readOnly="true" */ LDrawDragHandle activeDragHandle; // drag handle hit on last mouse-down (or private boolean isReadyToUse = false; // nil) /** * @uml.property name="gl2" * @uml.associationEnd readOnly="true" */ // ========== init // ============================================================== // // Purpose: Initialize the object. // // ============================================================================== public LDrawGLRenderer initWithBounds(Size2 boundsIn) { // self = [super init]; // ---------- Initialize instance variables // --------------------------------- ColorLibrary colorLibrary = (ColorLibrary) ColorLibrary .sharedColorLibrary(); setLDrawColor(colorLibrary.colorForCode(LDrawColorT.LDrawCurrentColor)); camera = new LDrawGLCamera(); isTrackingDrag = false; selectionMarquee = Box2.getZeroBox2(); rotationDrawMode = RotationDrawModeT.LDrawGLDrawNormal; gridSpacing = 20.0f; setViewOrientation(ViewOrientationT.ViewOrientation3D); return this; }// end initWithFrame: public LDrawGLRenderer initWithBoundsCamera(Size2 boundsIn, LDrawGLCamera ldrawCamera) { isReadyToUse = true; // self = [super init]; // ---------- Initialize instance variables // --------------------------------- ColorLibrary colorLibrary = (ColorLibrary) ColorLibrary .sharedColorLibrary(); setLDrawColor(colorLibrary.colorForCode(LDrawColorT.LDrawCurrentColor)); camera = ldrawCamera; isTrackingDrag = false; selectionMarquee = Box2.getZeroBox2(); rotationDrawMode = RotationDrawModeT.LDrawGLDrawNormal; gridSpacing = 20.0f; setViewOrientation(ViewOrientationT.ViewOrientation3D); return this; }// end initWithFrame: // ========== prepareOpenGL // ===================================================== // // Purpose: The context is all set up; this is where we prepare our OpenGL // state. // // ============================================================================== public void prepareOpenGL(GL2 gl2) { gl2.glEnable(GL2.GL_DEPTH_TEST); gl2.glEnable(GL2.GL_BLEND); gl2.glBlendFunc(GL2.GL_SRC_ALPHA, GL2.GL_ONE_MINUS_SRC_ALPHA); gl2.glEnable(GL2.GL_MULTISAMPLE); // antialiasing gl2.glEnable(GL2.GL_TEXTURE_2D); gl2.glEnable(GL2.GL_TEXTURE_GEN_S); gl2.glEnable(GL2.GL_TEXTURE_GEN_T); // This represents the "default" GL state, at least until we change that // policy. gl2.glEnableClientState(GL2.GL_VERTEX_ARRAY); gl2.glEnableClientState(GL2.GL_NORMAL_ARRAY); gl2.glEnableClientState(GL2.GL_COLOR_ARRAY); setBackgroundColor(gl2, 1.0f, 1.0f, 1.0f); // white // // Define the lighting. // // Our light position is transformed by the modelview matrix. That means // we need to have a standard model matrix loaded to get our light to // land in the right place! But our modelview might have already been // affected by someone calling -setViewOrientation:. So we restore the // default here. gl2.glMatrixMode(GL2.GL_MODELVIEW); gl2.glLoadIdentity(); gl2.glRotatef(180, 1, 0, 0); // convert to standard, upside-down LDraw // orientation. gl2.glShadeModel(GL2.GL_SMOOTH); gl2.glEnable(GL2.GL_NORMALIZE); gl2.glEnable(GL2.GL_COLOR_MATERIAL); // ---------- Light Model // --------------------------------------------------- // The overall scene has ambient light to make the lighting less harsh. // But // too much ambient light makes everything washed out. float lightModelAmbient[] = { 0.20f, 0.20f, 0.20f, 1.0f }; gl2.glLightModelf(GL2.GL_LIGHT_MODEL_TWO_SIDE, GL2.GL_FALSE); gl2.glLightModelfv(GL2.GL_LIGHT_MODEL_AMBIENT, FloatBuffer.wrap(lightModelAmbient)); // ---------- Lights // -------------------------------------------------------- // We are going to have two lights, one in a standard position (LIGHT0) // and // another pointing opposite to it (LIGHT1). The second light will // illuminate any inverted normals or backwards polygons. float position0[] = { 0f, 0.5f, 1f, 1.0f }; float position1[] = { 0f, -0.5f, -1f, 1.0f }; // Lessening the diffuseness also makes lighting less extreme. float light0Ambient[] = { 0.0f, 0.0f, 0.0f, 1.0f }; float light0Diffuse[] = { 1f, 1f, 1f, 1.0f }; float light0Specular[] = { 0.0f, 0.0f, 0.0f, 1.0f }; // normal forward light gl2.glLightfv(GL2.GL_LIGHT0, GL2.GL_POSITION, FloatBuffer.wrap(position0)); gl2.glLightfv(GL2.GL_LIGHT0, GL2.GL_AMBIENT, FloatBuffer.wrap(light0Ambient)); gl2.glLightfv(GL2.GL_LIGHT0, GL2.GL_DIFFUSE, FloatBuffer.wrap(light0Diffuse)); gl2.glLightfv(GL2.GL_LIGHT0, GL2.GL_SPECULAR, FloatBuffer.wrap(light0Specular)); gl2.glLightf(GL2.GL_LIGHT0, GL2.GL_CONSTANT_ATTENUATION, 1.0f); gl2.glLightf(GL2.GL_LIGHT0, GL2.GL_LINEAR_ATTENUATION, 0.0f); gl2.glLightf(GL2.GL_LIGHT0, GL2.GL_QUADRATIC_ATTENUATION, 0.0f); // opposing light to illuminate backward normals. gl2.glLightfv(GL2.GL_LIGHT1, GL2.GL_POSITION, FloatBuffer.wrap(position1)); gl2.glLightfv(GL2.GL_LIGHT1, GL2.GL_AMBIENT, FloatBuffer.wrap(light0Ambient)); gl2.glLightfv(GL2.GL_LIGHT1, GL2.GL_DIFFUSE, FloatBuffer.wrap(light0Diffuse)); gl2.glLightfv(GL2.GL_LIGHT1, GL2.GL_SPECULAR, FloatBuffer.wrap(light0Specular)); gl2.glLightf(GL2.GL_LIGHT1, GL2.GL_CONSTANT_ATTENUATION, 1.0f); gl2.glLightf(GL2.GL_LIGHT1, GL2.GL_LINEAR_ATTENUATION, 0.0f); gl2.glLightf(GL2.GL_LIGHT1, GL2.GL_QUADRATIC_ATTENUATION, 0.0f); gl2.glEnable(GL2.GL_LIGHTING); gl2.glEnable(GL2.GL_LIGHT0); gl2.glEnable(GL2.GL_LIGHT1); // Now that the light is positioned where we want it, we can restore the // correct viewing angle. setViewOrientation(viewOrientation); } // ========== draw // ============================================================== // // Purpose: Draw the LDraw content of the view. // // Notes: This method is, in theory at least, as thread-safe as Apple's // OpenGL implementation is. Which is to say, not very much. // // ============================================================================== public LDrawShaderRenderer ren; private boolean useWireFrame=false; public void draw(GL2 gl2) { // Make lines look a little nicer; Max width 1.0; 0.5 at 100% zoom gl2.glLineWidth((float) Math .min(zoomPercentageForGL() / 100 * 0.5, 1.0)); gl2.glMatrixMode(GL2.GL_PROJECTION); gl2.glLoadMatrixf(camera.getProjection(), 0); gl2.glMatrixMode(GL2.GL_MODELVIEW); gl2.glLoadMatrixf(camera.getModelView(), 0); if (ren == null) { ren = new LDrawShaderRenderer(); ren.useWireFrame = this.useWireFrame; } ren.initWithScale(gl2, zoomPercentageForGL() / 100.0f, camera.getModelView(), camera.getProjection()); gl2.glEnable(GL2.GL_DEPTH_TEST); gl2.glDepthMask(true); gl2.glDisable(GL2.GL_BLEND); ren.drawTransparent = false; fileBeingDrawn.drawSelf(gl2, ren); // gl2.glDisable(GL2.GL_CULL_FACE); gl2.glEnable(GL2.GL_BLEND); gl2.glDepthMask(false); gl2.glBlendFunc(GL2.GL_SRC_ALPHA, GL2.GL_ONE_MINUS_SRC_ALPHA); ren.drawTransparent = true; fileBeingDrawn.drawSelf(gl2, ren); gl2.glEnable(GL2.GL_DEPTH_TEST); gl2.glDepthMask(true); gl2.glEnable(GL2.GL_BLEND); if (delegate != null) delegate.LDrawGLRendererNeedsFlush(this); } private float zoomPercentageForGL() { if (locationMode() == LocationModeT.LocationModeWalkthrough) return 100.0f; return camera.zoomPercentage(); }// end draw:to // ========== isFlipped // ========================================================= // // Purpose: This lets us appear in the upper-left of scroll views // rather // than the bottom. The view should draw just fine whether or not // it is flipped, though. // // ============================================================================== public boolean isFlipped() { return true; }// end isFlipped // ========== isOpaque // ========================================================== // // Note: Our content completely covers this view. (This is just here // as a // reminder; NSOpenGLViews are opaque by default.) // // ============================================================================== public boolean isOpaque() { return true; } // ========== activeDragHandle // ================================================== // // Purpose: Returns a drag handle if we are currently locked into a // drag-handle drag. Otherwise returns nil. // // ============================================================================== public LDrawDragHandle activeDragHandle() { return activeDragHandle; } // ========== centerPoint // ======================================================= // // Purpose: Returns the point (in frame coordinates) which is // currently // at the center of the visible rectangle. This is useful for // determining the point being viewed in the scroll view. // // ============================================================================== public Vector2f centerPoint() { return MatrixMath.V2Make( MatrixMath.V2BoxMidX(scroller.getVisibleRect()), MatrixMath.V2BoxMidY(scroller.getVisibleRect())); }// end centerPoint // ========== didPartSelection // ================================================== // // Purpose: Returns whether the most-recent mouseDown resulted in a // part-selection attempt. This is only valid when called during a // mouse click. // // ============================================================================== public boolean didPartSelection() { return didPartSelection; } // ========== getInverseMatrix // ================================================== // // Purpose: Returns the inverse of the current modelview matrix. You // can // multiply points by this matrix to convert screen locations (or // vectors) to model points. // // Note: This function filters out the translation which is caused by // "moving" the camera with gluLookAt. That allows us to continue // working with the model as if it's positioned at the origin, // which means that points we generate with this matrix will // correspond to points in the LDraw model itself. // // ============================================================================== public Matrix4 getInverseMatrix() { Matrix4 transformation = MatrixMath.Matrix4CreateFromGLMatrix4(camera .getModelView()); Matrix4 inversed = MatrixMath.Matrix4Invert(transformation); return inversed; }// end getInverseMatrix // // // ========== getMatrix // ========================================================= // // Purpose: Returns the the current modelview matrix, basically. // // Note: This function filters out the translation which is caused by // "moving" the camera with gluLookAt. That allows us to continue // working with the model as if it's positioned at the origin, // which means that points we generate with this matrix will // correspond to points in the LDraw model itself. // // ============================================================================== public Matrix4 getMatrix() { return MatrixMath.Matrix4CreateFromGLMatrix4(camera.getModelView()); }// end getMatrix // ========== isTrackingDrag // ==================================================== // // Purpose: Returns YES if a mouse-drag is currently in progress. // // ============================================================================== /** * @return * @uml.property name="isTrackingDrag" */ public boolean isTrackingDrag() { return isTrackingDrag; } // // ========== LDrawColor // ======================================================== // // Purpose: Returns the LDraw color code of the receiver. // // ============================================================================== public LDrawColor getLDrawColor() { return color; }// end color // // ========== LDrawDirective // ==================================================== // // Purpose: Returns the file or model being drawn by this view. // // ============================================================================== public LDrawDirective getLDrawDirective() { return fileBeingDrawn; }// end LDrawDirective // ========== nudgeVector // ======================================================= // // Purpose: Returns the direction of a keyboard part nudge. The // target of // our nudgeAction queries this method to find how to nudge the // selection. // // Notes: This value is only valid during the nudgeAction callback. // // ============================================================================== public Vector3f nudgeVector() { return nudgeVector; }// end nudgeVector // ========== projectionMode // ==================================================== // // Purpose: Returns the current projection mode (perspective or // orthographic) used in the view. // // ============================================================================== public ProjectionModeT projectionMode() { return camera.projectionMode(); }// end projectionMode // ========== locationMode // ==================================================== // // Purpose: Returns the current location mode (model or walkthrough). // // ============================================================================== public LocationModeT locationMode() { return camera.locationMode(); }// end locationMode // ========== selectionMarquee // ================================================== // ============================================================================== public Box2 selectionMarquee() { return selectionMarquee; } // ========== viewingAngle // ====================================================== // // Purpose: Returns the modelview rotation, in degrees. // // Notes: These numbers do *not* include the fact that LDraw has an // upside-down coordinate system. So if this method returns // (0,0,0), that means "Front, looking right-side up." // // ============================================================================== public Vector3f viewingAngle() { return camera.viewingAngle(); }// end viewingAngle // ========== viewOrientation // =================================================== // // Purpose: Returns the current camera orientation for this view. // // ============================================================================== public ViewOrientationT viewOrientation() { return viewOrientation; }// end viewOrientation // ========== viewport // ========================================================== // // Purpose: Returns the viewport. Origin is the lower-left. // // ============================================================================== public Box2 getViewport() { Box2 viewport = Box2.getZeroBox2(); viewport.size = scroller.getMaxVisibleSizeGL(); return viewport; } // ========== zoomPercentage // ==================================================== // // Purpose: Returns the percentage magnification being applied to the // receiver. (200 means 2x magnification.) This is the 'nominal' // zoom the user sees - it should be used by UI and tool code. // // ============================================================================== public float zoomPercentage() { return camera.zoomPercentage(); }// end zoomPercentage // ========== zoomPercentage // ==================================================== // // Purpose: Returns the percentage magnification being applied to // drawing; // this represents the scale from GL viewport coordiantes (which // are always window manager pixels) to NS document coordinates // (which DO get scaled). // // Use this routine to convert between NS view and GL viewport // coordinates. // // Notes: When walk-through is engaged, zoom controls the camera FOV // but // leaves the document untouched at window size. So this routine // checks the camera mode and just returns 100.0. // // ============================================================================== public float getZoomPercentageForGL() { if (locationMode() == LocationModeT.LocationModeWalkthrough) return 100.0f; return camera.zoomPercentage(); }// end zoomPercentageForGL // ========== setAllowsEditing: // ================================================= // // Purpose: Sets whether the renderer supports part selection and // dragging. // // Notes: Querying a delegate isn't sufficient. // // ============================================================================== /** * @param flag * @uml.property name="allowsEditing" */ public void setAllowsEditing(boolean flag) { allowsEditing = flag; } // ========== setDelegate: // ====================================================== // // Purpose: Sets the object that acts as the delegate for the // receiver. // // This object relies on the the delegate to interface with the // window manager to do things like scrolling. // // ============================================================================== /** * @param object * @param newScroller * @uml.property name="delegate" */ public void setDelegate(LDrawGLRendererDelegate object, ILDrawGLCameraScroller newScroller) { // weak link. delegate = object; scroller = newScroller; camera.setScroller(newScroller); }// end setDelegate: // ========== setBackAction: // ==================================================== // // Purpose: Sets the method called on the target when a backward // swipe is // received. // // ============================================================================== /** * @param newAction * @uml.property name="backAction" */ public void setBackAction(SelT newAction) { backAction = newAction; }// end setBackAction: // ========== setBackgroundColorRed:green:blue: // ================================= // // Purpose: Sets the canvas background color. // // ============================================================================== public void setBackgroundColor(GL2 gl2, float red, float green, float blue) { glBackgroundColor[0] = red; glBackgroundColor[1] = green; glBackgroundColor[2] = blue; glBackgroundColor[3] = 1.0f; gl2.glClearColor(glBackgroundColor[0], glBackgroundColor[1], glBackgroundColor[2], glBackgroundColor[3]); if (delegate != null) delegate.LDrawGLRendererNeedsRedisplay(this); } // ========== setDragEndedInOurDocument: // ======================================== // // Purpose: When a dragging operation we initiated ends outside the // originating document, we need to know about it so that we can // tell the document to completely delete the directives it started // dragging. (They are merely hidden during the drag.) However, // each document can be represented by multiple views, so it is // insufficient to simply test whether the drag ended within this // view. // // So, when a drag ends in any LDrawGLView, it inspects the // dragging source to see if it represents the same document. If it // does, it sends the source this message. If this message hasn't // been received by the time the drag ends, this view will // automatically instruct its document to purge the source // directives, since the directives were actually dragged out of // their document. // // ============================================================================== /** * @param flag * @uml.property name="dragEndedInOurDocument" */ public void setDragEndedInOurDocument(boolean flag) { dragEndedInOurDocument = flag; }// end setDragEndedInOurDocument: // ========== setDraggingOffset: // ================================================ // // Purpose: Sets the offset to apply to the first drag-and-drop // part's // position. This is used when initiating drag-and-drop while // clicking on a point other than the exact center of the part. We // want to maintain the clicked point under the cursor, but it is // internally easier to move the part's centerpoint. This offset // allows us to translate between the two. // // ============================================================================== /** * @param offsetIn * @uml.property name="draggingOffset" */ public void setDraggingOffset(Vector3f offsetIn) { draggingOffset = offsetIn; } // ========== setForwardAction: // ================================================= // // Purpose: Sets the method called on the target when a forward swipe // is // received. // // ============================================================================== /** * @param newAction * @uml.property name="forwardAction" */ public void setForwardAction(SelT newAction) { forwardAction = newAction; }// end setForwardAction: // ========== setGridSpacing: // =================================================== // // Purpose: Sets the grid amount by which things are dragged. // // ============================================================================== /** * @param newValue * @uml.property name="gridSpacing" */ public void setGridSpacing(float newValue) { gridSpacing = newValue; } // ========== setLDrawColor: // ==================================================== // // Purpose: Sets the base color for parts drawn by this view which // have // no // color themselves. // // ============================================================================== public void setLDrawColor(LDrawColor newColor) { // newColor.retain(); color = newColor; if (delegate != null) delegate.LDrawGLRendererNeedsRedisplay(this); }// end setColor // ========== LDrawDirective: // =================================================== // // Purpose: Sets the file being drawn in this view. // // We also do other housekeeping here associated with tracking the // model. We also automatically center the model in the view. // // ============================================================================== public void setLDrawDirective(LDrawDirective newFile) { boolean virginView = (fileBeingDrawn == null); Box3 bounds = Box3.getInvalidBox(); // Update our variable. fileBeingDrawn = newFile; if (newFile != null) bounds = newFile.boundingBox3(); if (camera != null) camera.setModelSize(bounds); if (delegate != null) delegate.LDrawGLRendererNeedsRedisplay(this); if (virginView == true) { scrollModelPoint(Vector3f.getZeroVector3f(), MatrixMath.V2Make(0.5f, 0.5f)); } // Register for important notifications. // NotificationCenter notificationCenter = NotificationCenter // .getInstance(); // notificationCenter.removeSubscriber(this, // NotificationMessageT.LDrawDirectiveDidChange); // notificationCenter.removeSubscriber(this, // NotificationMessageT.LDrawFileActiveModelDidChange); // notificationCenter.removeSubscriber(this, // NotificationMessageT.LDrawModelRotationCenterDidChange); // // notificationCenter.addSubscriber(this, // NotificationMessageT.LDrawDirectiveDidChange); // notificationCenter.addSubscriber(this, // NotificationMessageT.LDrawFileActiveModelDidChange); // notificationCenter.addSubscriber(this, // NotificationMessageT.LDrawModelRotationCenterDidChange); updateRotationCenter(); } // ========== setMaximumVisibleSize: // ============================================ // // Purpose: Sets the largest size (in frame coordinates) to which the // visible rect should be permitted to grow. // // ============================================================================== public void setMaximumVisibleSize(Size2 size) { camera.tickle(); delegate.LDrawGLRendererNeedsRedisplay(this); } // ========== setNudgeAction: // =================================================== // // Purpose: Sets the action sent when the GLView wants to nudge a // part. // // You get the nudge vector by calling -nudgeVector within the body // of the action method. // // ============================================================================== /** * @param newAction * @uml.property name="nudgeAction" */ public void setNudgeAction(SelT newAction) { nudgeAction = newAction; }// end setNudgeAction: // ========== setProjectionMode: // ================================================ // // Purpose: Sets the projection used when drawing the receiver: // - orthographic is like a Mercator map; it distorts deeper // objects. // - perspective draws deeper objects toward a vanishing point; // this is how humans see the world. // // ============================================================================== public void setProjectionMode(ProjectionModeT newProjectionMode) { camera.setProjectionMode(newProjectionMode); delegate.LDrawGLRendererNeedsRedisplay(this); } // end setProjectionMode: // ========== setLocationMode: // ================================================ // // Purpose: Sets the location mode used when drawing the receiver. // - model points the camera at the model center from a distance. // - walk-through puts the camera _on_ the model center. // // ============================================================================== public void setLocationMode(LocationModeT newLocationMode) { camera.setLocationMode(newLocationMode); delegate.LDrawGLRendererNeedsRedisplay(this); } // end setLocationMode: // ========== setSelectionMarquee: // ============================================== // // Purpose: The box (in view coordinates) in which to draw the // selection // marquee. // // ============================================================================== /** * @param newBox_view * @uml.property name="selectionMarquee" */ public void setSelectionMarquee(Box2 newBox_view) { selectionMarquee = newBox_view; } // ========== setTarget: // ======================================================== // // Purpose: Sets the object which is the receiver of this view's // action // methods. // // ============================================================================== /** * @param newTarget * @uml.property name="target" */ public void setTarget(Object newTarget) { target = newTarget; }// end setTarget: // ========== setViewingAngle: // ================================================== // // Purpose: Sets the modelview rotation, in degrees. The angle is // // applied // in // x-y-z order. // // Notes: These numbers do *not* include the fact that LDraw has an // upside-down coordinate system. So if this method returns // (0,0,0), that means "Front, looking right-side up." // // ============================================================================== public void setViewingAngle(Vector3f newAngle) { camera.setViewingAngle(newAngle); if (delegate != null) delegate.LDrawGLRendererNeedsRedisplay(this); } // end setViewingAngle: // ========== setViewOrientation: // =============================================== // // Purpose: Changes the camera position from which we view the model. // i.e., ViewOrientationFront means we see the model head-on. // // ============================================================================== /** * @param newOrientation * @uml.property name="viewOrientation" */ public void setViewOrientation(ViewOrientationT newOrientation) { Vector3f newAngle = LDrawUtilities .angleForViewOrientation(newOrientation); viewOrientation = newOrientation; // Apply the angle itself. setViewingAngle(newAngle); if (delegate != null) delegate.LDrawGLRendererNeedsRedisplay(this); }// end setViewOrientation: // ========== setZoomPercentage: // ================================================ // // Purpose: Enlarges (or reduces) the magnification on this view. The // center // point of the original magnification remains the center point of // the new magnification. Does absolutely nothing if this view // isn't contained within a scroll view. // // Parameters: newPercentage: new zoom; pass 100 for 100%, etc. // Automatically // constrained to a minimum of 1%. // // ============================================================================== public void setZoomPercentage(float newPercentage) { camera.setZoomPercentage(newPercentage); } // ========== moveCamera: // ======================================================= // // Purpose: Moves the camera's rotation center by a fixed offset. // Used to // walk around the walk-through camera, or to change the model's // center of rotation for the model camera. // // ============================================================================== public void moveCamera(Vector3f delta) { camera.setRotationCenter(MatrixMath.V3Add(camera.rotationCenter(), delta)); if (delegate != null) delegate.LDrawGLRendererNeedsRedisplay(this); }// end moveCamera // ========== zoomIn: // =========================================================== // // Purpose: Enlarge the scale of the current LDraw view. // // ============================================================================== public void zoomIn(Object sender) { float currentZoom = zoomPercentage(); float newZoom = currentZoom * 2; setZoomPercentage(newZoom); }// end zoomIn: // ========== zoomOut: // ========================================================== // // Purpose: Shrink the scale of the current LDraw view. // // ============================================================================== public void zoomOut(Object sender) { float currentZoom = zoomPercentage(); float newZoom = currentZoom / 2; setZoomPercentage(newZoom); }// end zoomOut: // ========== zoomToFit: // ======================================================== // // Purpose: Enlarge or shrink the zoom and scroll the model such that // its // image perfectly fills the visible area of the view // // ============================================================================== // public void zoomToFit(Object sender) // { // Size2 maxContentSize = Size2.getZeroSize2(); // Box3 boundingBox = Box3.getInvalidBox(); // Point3 center = Point3.getZeroPoint3(); // Matrix4 modelView = Matrix4.getIdentityMatrix4(); // Matrix4 projection = Matrix4.getIdentityMatrix4(); // Box2 viewport = Box2.getZeroBox2(); // Box3 projectedBounds = Box3.getInvalidBox(); // Box2 projectionRect = Box2.getZeroBox2(); // Size2 zoomScale2D = Size2.getZeroSize2(); // float zoomScaleFactor = 0.0f; // // // How many onscreen pixels do we have to work with? // maxContentSize.setWidth(MatrixMath.V2BoxWidth(scroller.getVisibleRect()) // * zoomPercentage()/100.0); // maxContentSize.setHeight(MatrixMath.V2BoxHeight(scroller.getVisibleRect()) // *zoomPercentage()/100.0); // // NSLog(@"windowVisibleRect = %@", // //NSStringFromRect(windowVisibleRect)); // // NSLog(@"maxContentSize = %@", NSStringFromSize(maxContentSize)); // // //�좎떎�몄삕 �좎뙏�듭삕�좎룞��.. �좎룞�쇿뜝�쒕벝��李썲뜝�숈삕�좎룞���좎룞�쇿뜝�숈삕 �좎룞���좎떍�몄삕 泥섇뜝�숈삕 // �좎떦�붾벝���좎떦�붾벝��.. // // Get bounds // if(fileBeingDrawn.respondsToSelector(boundingBox3)) // { // boundingBox = [(id)self->fileBeingDrawn boundingBox3]; // if(V3EqualBoxes(boundingBox, InvalidBox) == NO) // { // // Project the bounds onto the 2D "canvas" // modelView = Matrix4CreateFromGLMatrix4([camera getModelView]); // projection = Matrix4CreateFromGLMatrix4([camera getProjection]); // viewport = [self viewport]; // // projectedBounds = [(id)self->fileBeingDrawn // projectedBoundingBoxWithModelView:modelView // projection:projection // view:viewport ]; // projectionRect = V2MakeBox(projectedBounds.min.x, // projectedBounds.min.y, // // origin // projectedBounds.max.x - projectedBounds.min.x, // width // projectedBounds.max.y - projectedBounds.min.y); // height // // // //---------- Find zoom scale ----------------------------------- // // Completely fill the viewport with the image // // zoomScale2D.width = maxContentSize.width / // V2BoxWidth(projectionRect); // zoomScale2D.height = maxContentSize.height / // V2BoxHeight(projectionRect); // // zoomScaleFactor = MIN(zoomScale2D.width, zoomScale2D.height); // // // //---------- Find visual center point -------------------------- // // One might think this would be V3CenterOfBox(bounds). But it's // // not. It seems perspective distortion can cause the visual // // center of the model to be someplace else. // // Point2 graphicalCenter_viewport = V2Make( V2BoxMidX(projectionRect), // V2BoxMidY(projectionRect) ); // Point2 graphicalCenter_view = [self // convertPointFromViewport:graphicalCenter_viewport]; // Point3 graphicalCenter_model = ZeroPoint3; // // graphicalCenter_model = [self modelPointForPoint:graphicalCenter_view // depthReferencePoint:center]; // // // //---------- Zoom to Fit! -------------------------------------- // // [self setZoomPercentage:([self zoomPercentage] * zoomScaleFactor)]; // [self scrollCenterToModelPoint:graphicalCenter_model]; // } // } // // }//end zoomToFit: // // // // #pragma mark - // #pragma mark EVENTS // #pragma mark - // // //========== mouseMoved: // ======================================================= // // // // Purpose: Mouse has moved to the given view point. (This method is // // optional.) // // // //============================================================================== // - (void) mouseMoved:(Point2)point_view // { // [self publishMouseOverPoint:point_view]; // } // // // //========== mouseDown // ========================================================= // // // // Purpose: Signals that a mouse-down has been received; clear // various // state // // flags in preparation for selection or dragging. // // // // Note: Our platform view is responsible for correct interpretation // of // // the event and routing it to the appropriate methods in the // // renderer class. // // // //============================================================================== // - (void) mouseDown // { // // Reset event tracking flags. // self->isTrackingDrag = NO; // self->didPartSelection = NO; // // // This might be the start of a new drag; start collecting frames per // second // fpsStartTime = [NSDate timeIntervalSinceReferenceDate]; // framesSinceStartTime = 0; // // [self->delegate markPreviousSelection:self]; // } // // // //========== mousedDragged // ===================================================== // // // // Purpose: Signals that a mouse-drag has been received; clear // various // state // // flags in preparation for selection or dragging. // // // // Note: Our platform view is responsible for correct interpretation // of // // the event and routing it to the appropriate methods in the // // renderer class. // // // //============================================================================== // - (void) mouseDragged // { // self->isStartingDrag = (self->isTrackingDrag == NO); // first drag if // none to date // self->isTrackingDrag = YES; // } // // // //========== mouseUp // =========================================================== // // // // Purpose: Signals that a mouse-up has been received; clear various // state // // flags in preparation for selection or dragging. // // // // Note: Our platform view is responsible for correct interpretation // of // // the event and routing it to the appropriate methods in the // // renderer class. // // // //============================================================================== // - (void) mouseUp // { // // Redraw from our dragging operations, if necessary. // if( (self->isTrackingDrag == YES && rotationDrawMode == // LDrawGLDrawExtremelyFast) // || V2BoxWidth(self->selectionMarquee) || // V2BoxHeight(self->selectionMarquee) ) // { // [self->delegate LDrawGLRendererNeedsRedisplay:self]; // } // // self->activeDragHandle = nil; // self->isTrackingDrag = NO; //not anymore. // self->selectionMarquee = ZeroBox2; // // [self->delegate unmarkPreviousSelection:self]; // } // // // #pragma mark - Clicking // // //========== mouseCenterClick: // ================================================= // // // // Purpose: We have received a mouseDown event which is intended to // center // // our view on the point clicked. // // // //============================================================================== // - (void) mouseCenterClick:(Point2)viewClickedPoint // { // // Ben says: this function used to have a special case for // ortho-viewing. // // But since perspective-case code is fully general, we just now use // it // alway. // // // // Perspective distortion makes this more complicated. The camera is // in // // a fixed position, but the frustum changes with the scrollbars. // // We need to calculate the world point we just clicked on, then // derive // // a new frustum projection centered on that point. // Point3 clickedPointInModel = ZeroPoint3; // // // Find the point we clicked on. It would be more accurate to use // // -getDirectivesUnderMouse:::, but it has to actually draw parts, // which // // can be slow. // clickedPointInModel = [self modelPointForPoint:viewClickedPoint]; // // [self scrollCenterToModelPoint:clickedPointInModel]; // // }//end mouseCenterClick: // // // //========== mouseSelectionClick:extendSelection: // ============================== // // // // Purpose: Time to see if we should select something in the model. // We // // search the model geometry for intersection with the click point. // // Our delegate is responsible for managing the actual selection. // // // // This function returns whether it hit something - calling code can // // then do a part drag or marquee based on whether the user clicked // // on a part or on empty space. // // // //============================================================================== // - (BOOL) mouseSelectionClick:(Point2)point_view // selectionMode:(SelectionModeT)selectionMode // { // LDrawDirective *clickedDirective = nil; // // self->selectionMarquee = V2MakeBox(point_view.x, point_view.y, 0, 0); // // // Only try to select if we are actually drawing something, and can // actually // // select it. // if( self->fileBeingDrawn != nil // && self->allowsEditing == YES // && [self->delegate // respondsToSelector:@selector(LDrawGLRenderer:wantsToSelectDirective:byExtendingSelection:)] // ) // { // Point2 point_viewport = [self convertPointToViewport:point_view]; // Point2 bl = // V2Make(point_viewport.x-HANDLE_SIZE,point_viewport.y-HANDLE_SIZE); // Point2 tr = // V2Make(point_viewport.x+HANDLE_SIZE,point_viewport.y+HANDLE_SIZE); // GLfloat depth = 1.0; // // Box2 viewport = [self viewport]; // // Get view and projection // Point2 point_clip = V2Make( (point_viewport.x - viewport.origin.x) * // 2.0 // / V2BoxWidth(viewport) - 1.0, // (point_viewport.y - viewport.origin.y) * 2.0 / V2BoxHeight(viewport) // - // 1.0 ); // // float x1 = (MIN(bl.x,tr.x) - viewport.origin.x) * 2.0 / V2BoxWidth // (viewport) - 1.0; // float x2 = (MAX(bl.x,tr.x) - viewport.origin.x) * 2.0 / V2BoxWidth // (viewport) - 1.0; // float y1 = (MIN(bl.y,tr.y) - viewport.origin.x) * 2.0 / // V2BoxHeight(viewport) - 1.0; // float y2 = (MAX(bl.y,tr.y) - viewport.origin.y) * 2.0 / // V2BoxHeight(viewport) - 1.0; // // Box2 test_box = V2MakeBoxFromPoints( V2Make(x1, y1), V2Make(x2, y2) // ); // // Matrix4 mvp = Matrix4Multiply( // Matrix4CreateFromGLMatrix4([camera getModelView]), // Matrix4CreateFromGLMatrix4([camera getProjection])); // // id bestObject = nil; // [fileBeingDrawn depthTest:point_clip inBox:test_box transform:mvp // creditObject:nil bestObject:&bestObject bestDepth:&depth]; // // clickedDirective = bestObject; // // // Primitive manipulation? // if([clickedDirective isKindOfClass:[LDrawDragHandle class]]) // { // self->activeDragHandle = (LDrawDragHandle*)clickedDirective; // } // else // { // // Normal selection // self->activeDragHandle = nil; // // // If we end up actually selecting some single thing, the extension // happens if we are intersection (option-shift) or extend (shift). // BOOL extendSelection = selectionMode == SelectionExtend || // selectionMode // == SelectionIntersection; // // BOOL has_sel_directive = clickedDirective != nil && [clickedDirective // isSelected]; // BOOL has_any_directive = clickedDirective != nil; // // switch(selectionMode) // { // case SelectionReplace: // // Replacement mode? Select unless we hit an already hit one - we do // not // "deselect others" on a click. // if(!has_sel_directive) // [self->delegate LDrawGLRenderer:self // wantsToSelectDirective:clickedDirective // byExtendingSelection:extendSelection ]; // break; // // case SelectionExtend: // // Extended selection. If we hit a part, toggle it - if we miss a // part, // don't do anything, nothing to do. // if(has_any_directive) // [self->delegate LDrawGLRenderer:self // wantsToSelectDirective:clickedDirective // byExtendingSelection:extendSelection ]; // break; // // case SelectionIntersection: // // Intersection. If we hit an unselected directive, do the select to // grab // it - this will grab it (via option-shift). // // Then we copy. If we have no directive, the whole sel clears, which // is // the correct start for an intersection (since the // // marquee is empty). // if(!has_sel_directive) // [self->delegate LDrawGLRenderer:self // wantsToSelectDirective:clickedDirective // byExtendingSelection:extendSelection ]; // break; // // case SelectionSubtract: // // Subtraction. If we have an UNSELECTED directive, we have to grab // it. // If we have a selected directive we do nothing so // // we can option-drag-copy thes el. And if we just miss everything, // the // subtraction hasn't nuked anything yet...again we do nothing. // if(has_any_directive && !has_sel_directive) // [self->delegate LDrawGLRenderer:self // wantsToSelectDirective:clickedDirective // byExtendingSelection:extendSelection ]; // break; // } // } // } // // self->didPartSelection = YES; // // return (clickedDirective == nil) ? NO : YES; // // }//end mousePartSelection: // // // //========== mouseZoomInClick: // ================================================= // // // // Purpose: Depending on the tool mode, we want to zoom in or out. We // also // // want to center the view on whatever we clicked on. // // // //============================================================================== // - (void) mouseZoomInClick:(Point2)viewClickedPoint // { // CGFloat currentZoom = [self zoomPercentage]; // CGFloat newZoom = currentZoom * 2; // // [self setZoomPercentage:newZoom preservePoint:viewClickedPoint]; // // }//end mouseZoomInClick: // // // //========== mouseZoomOutClick: // ================================================ // // // // Purpose: Depending on the tool mode, we want to zoom in or out. We // also // // want to center the view on whatever we clicked on. // // // //============================================================================== // - (void) mouseZoomOutClick:(Point2)viewClickedPoint // { // CGFloat currentZoom = [self zoomPercentage]; // CGFloat newZoom = currentZoom / 2; // // [self setZoomPercentage:newZoom preservePoint:viewClickedPoint]; // // }//end mouseZoomOutClick: // // // #pragma mark - Dragging // // //========== dragHandleDragged: // ================================================ // // // // Purpose: Move the active drag handle // // // //============================================================================== // - (void) dragHandleDraggedToPoint:(Point2)point_view // constrainDragAxis:(BOOL)constrainDragAxis // { // Point3 modelReferencePoint = [self->activeDragHandle position]; // BOOL moved = NO; // // [self publishMouseOverPoint:point_view]; // // // Give the document controller an opportunity for undo management! // if(self->isStartingDrag && [self->delegate // respondsToSelector:@selector(LDrawGLRenderer:willBeginDraggingHandle:)]) // { // [self->delegate LDrawGLRenderer:self // willBeginDraggingHandle:self->activeDragHandle]; // } // // // Update with new position // moved = [self updateDirectives:[NSArray // arrayWithObject:self->activeDragHandle] // withDragPosition:point_view // depthReferencePoint:modelReferencePoint // constrainAxis:constrainDragAxis]; // // if(moved) // { // if([self->fileBeingDrawn // respondsToSelector:@selector(optimizeVertexes)]) // { // [(id)self->fileBeingDrawn optimizeVertexes]; // } // // [self->fileBeingDrawn noteNeedsDisplay]; // // if([self->delegate // respondsToSelector:@selector(LDrawGLRenderer:dragHandleDidMove:)]) // { // [self->delegate LDrawGLRenderer:self // dragHandleDidMove:self->activeDragHandle]; // } // } // // }//end dragHandleDragged: // // // //========== panDragged:location: // ============================================== // // // // Purpose: Scroll the view as the mouse is dragged across it. // // // //============================================================================== // - (void) panDragged:(Vector2)viewDirection // location:(Point2)point_view // { // if(isStartingDrag) // { // self->initialDragLocation = [self modelPointForPoint:point_view]; // } // // Box2 viewport = [self viewport]; // Point2 point_viewport = [self convertPointToViewport:point_view]; // Point2 proportion = V2Make(point_viewport.x, point_viewport.y); // // proportion.x /= V2BoxWidth(viewport); // proportion.y /= V2BoxHeight(viewport); // // if([self->delegate // respondsToSelector:@selector(LDrawGLRendererMouseNotPositioning:)]) // [self->delegate LDrawGLRendererMouseNotPositioning:self]; // // [self scrollModelPoint:self->initialDragLocation // toViewportProportionalPoint:proportion]; // // }//end panDragged: // // // //========== rotationDragged: // ================================================== // // // // Purpose: Tis time to rotate the object! // // // // We need to translate horizontal and vertical 2-dimensional mouse // // drags into 3-dimensional rotations. // // // // +---------------------------------+ /// /- -\ \\\ (This thing is a // sphere.) // // | y /|\ | / / \ \ . // // | | | // / \ \\ . // // | |vertical | | /--+-----+-\ | // // | |motion (around x) |/// | | \\\| // // | | x | | | | | // // |<---------------+--------------->| | | | | // // | | horizontal | |\\\ | | ///| // // | | motion | | \--+-----+-/ | // // | | (around y) | \\ | | // // // | | | \ \ / / // // | \|/ | \\\ \ / /// // // +---------------------------------+ -------- // // // // But 2D motion is not 3D motion! We can't just say that // // horizontal drag = rotation around y (up) axis. Why? Because the // // y-axis may be laying horizontally due to the rotation! // // // // The trick is to convert the y-axis *on the projection screen* // // back to a *vector in the model*. Then we can just call glRotate // // around that vector. The result that the model is rotated in the // // direction we dragged, no matter what its orientation! // // // // Last Note: A horizontal drag from left-to-right is a // // counterclockwise rotation around the projection's y axis. // // This means a positive number of degrees caused by a positive // // mouse displacement. // // But, a vertical drag from bottom-to-top is a clockwise // // rotation around the projection's x-axis. That means a // // negative number of degrees cause by a positive mouse // // displacement. That means we must multiply our x-rotation by // // -1 in order to make it go the right direction. // // // //============================================================================== // - (void) rotationDragged:(Vector2)viewDirection // { // if([self projectionMode] != ProjectionModePerspective) // { // [self setProjectionMode:ProjectionModePerspective]; // self->viewOrientation = ViewOrientation3D; // } // // [camera rotationDragged:viewDirection]; // // if([self->delegate // respondsToSelector:@selector(LDrawGLRendererMouseNotPositioning:)]) // [self->delegate LDrawGLRendererMouseNotPositioning:self]; // // [self->delegate LDrawGLRendererNeedsRedisplay:self]; // // // }//end rotationDragged // // //========== zoomDragged: // ====================================================== // // // // Purpose: Drag up means zoom in, drag down means zoom out. 1 px = 1 // %. // // // //============================================================================== // - (void) zoomDragged:(Vector2)viewDirection // { // CGFloat pixelChange = -viewDirection.y; // Negative means down // CGFloat magnification = pixelChange/100; // 1 px = 1% // CGFloat zoomChange = 1.0 + magnification; // CGFloat currentZoom = [self zoomPercentage]; // // [self setZoomPercentage:(currentZoom * zoomChange)]; // // if([self->delegate // respondsToSelector:@selector(LDrawGLRendererMouseNotPositioning:)]) // [self->delegate LDrawGLRendererMouseNotPositioning:self]; // // }//end zoomDragged: // // // //========== mouseSelectionDragToPoint:extendSelection: // ======================== // // // // Purpose: Selects objects under the dragged rectangle. Caller code // tracks // // the rectangle itself. // // // //============================================================================== // - (void) mouseSelectionDragToPoint:(Point2)point_view // selectionMode:(SelectionModeT) selectionMode // { // #if TIME_BOXTEST // NSDate * startTime = [NSDate date]; // #endif // // #if WANT_TWOPASS_BOXTEST // NSArray *fastDrawParts = nil; // #endif // NSArray *fineDrawParts = nil; // // self->selectionMarquee = V2MakeBoxFromPoints(selectionMarquee.origin, // point_view); // // // Only try to select if we are actually drawing something, and can // actually // // select it. // if( self->fileBeingDrawn != nil // && self->allowsEditing == YES // && [self->delegate // respondsToSelector:@selector(LDrawGLRenderer:wantsToSelectDirective:byExtendingSelection:)] // ) // { // // First do hit-testing on nothing but the bounding boxes; that is // very // // fast and likely eliminates a lot of parts. // // #if WANT_TWOPASS_BOXTEST // fastDrawParts = [self getDirectivesUnderRect:self->selectionMarquee // amongDirectives:[NSArray arrayWithObject:self->fileBeingDrawn] // fastDraw:YES]; // // fineDrawParts = [self getDirectivesUnderRect:self->selectionMarquee // amongDirectives:fastDrawParts // fastDraw:NO]; // #else // fineDrawParts = [self getDirectivesUnderRect:self->selectionMarquee // amongDirectives:[NSArray arrayWithObject:self->fileBeingDrawn] // fastDraw:NO]; // #endif // [self->delegate LDrawGLRenderer:self // wantsToSelectDirectives:fineDrawParts // selectionMode:selectionMode ]; // // } // // #if TIME_BOXTEST // NSTimeInterval drawTime = -[startTime timeIntervalSinceNow]; // printf("Box: %lf\n", drawTime); // #endif // // self->didPartSelection = YES; // // }//end mouseSelectionDrag:to:extendSelection: // // // #pragma mark - // #pragma mark Gestures // // //========== beginGesture // ====================================================== // // // // Purpose: Our platform host view is informing us that it is // starting // // gesture tracking. // // // //============================================================================== // - (void) beginGesture // { // self->isGesturing = YES; // } // // // //========== endGesture // ======================================================== // // // // Purpose: Our platform host view is informing us that it is ending // // gesture tracking. // // // //============================================================================== // - (void) endGesture // { // self->isGesturing = NO; // // if(self->rotationDrawMode == LDrawGLDrawExtremelyFast) // { // [self->delegate LDrawGLRendererNeedsRedisplay:self]; // } // } // // // //========== rotateWithEvent: // ================================================== // // // // Purpose: User is doing the twist (rotate) trackpad gesture. Rotate // // counterclockwise by the given degrees. // // // // I have decided to interpret this as spinning the "baseplate" // // plane of the model (that is, spinning around -y). // // // //============================================================================== // - (void) rotateByDegrees:(float)angle // { // if([self projectionMode] != ProjectionModePerspective) // { // [self setProjectionMode:ProjectionModePerspective]; // self->viewOrientation = ViewOrientation3D; // } // // [camera rotateByDegrees:angle]; // // }//end rotateWithEvent: // // // #pragma mark - // #pragma mark DRAG AND DROP // #pragma mark - // // //========== draggingEnteredAtPoint: // =========================================== // // // // Purpose: A drag-and-drop part operation entered this view. We need // to // // initiate interactive dragging. // // // //============================================================================== // - (void) draggingEnteredAtPoint:(Point2)point_view // directives:(NSArray *)directives // setTransform:(BOOL)setTransform // originatedLocally:(BOOL)originatedLocally // { // LDrawDrawableElement *firstDirective = [directives objectAtIndex:0]; // LDrawPart *newPart = nil; // TransformComponents partTransform = IdentityComponents; // Point3 modelReferencePoint = ZeroPoint3; // // //---------- Initialize New Part? // ------------------------------------------ // // if(setTransform == YES) // { // // Uninitialized elements are always new parts from the part browser. // newPart = [directives objectAtIndex:0]; // // // Ask the delegate roughly where it wants us to be. // // We get a full transform here so that when we drag in new parts, // they // // will be rotated the same as whatever part we were using last. // if([self->delegate // respondsToSelector:@selector(LDrawGLRendererPreferredPartTransform:)]) // { // partTransform = [self->delegate // LDrawGLRendererPreferredPartTransform:self]; // [newPart setTransformComponents:partTransform]; // } // } // // // //---------- Find Location // ------------------------------------------------- // // We need to map our 2-D mouse coordinate into a point in the // model's // 3-D // // space. // // modelReferencePoint = [firstDirective position]; // // // Apply the initial offset. // // This is the difference between the position of part 0 and the // actual // // clicked point. We do this so that the point you clicked always // remains // // directly under the mouse. // // // // Only applicable if dragging into the source view. Other views may // have // // different orientations. We might be able to remove that // requirement by // // zeroing the inapplicable component. // if(originatedLocally == YES) // { // modelReferencePoint = V3Add(modelReferencePoint, // self->draggingOffset); // } // else // { // [self setDraggingOffset:ZeroPoint3]; // no offset for future updates // either // } // // // For constrained dragging, we care only about the initial, // unmodified // // postion. // self->initialDragLocation = modelReferencePoint; // // // Move the parts // [self updateDirectives:directives // withDragPosition:point_view // depthReferencePoint:modelReferencePoint // constrainAxis:NO]; // // // The drag has begun! // if([self->fileBeingDrawn // respondsToSelector:@selector(setDraggingDirectives:)]) // { // [(id)self->fileBeingDrawn setDraggingDirectives:directives]; // // [self->fileBeingDrawn noteNeedsDisplay]; // } // // }//end draggingEntered: // // // //========== endDragging // ======================================================= // // // // Purpose: Ends part drag-and-drop. // // // //============================================================================== // - (void) endDragging // { // if([self->fileBeingDrawn // respondsToSelector:@selector(setDraggingDirectives:)]) // { // [(id)self->fileBeingDrawn setDraggingDirectives:nil]; // // [self->fileBeingDrawn noteNeedsDisplay]; // } // } // // // //========== updateDragWithPosition:constrainAxis: // ============================= // // // // Purpose: Adjusts the directives so they align with the given drag // // location, in window coordinates. // // // //============================================================================== // - (void) updateDragWithPosition:(Point2)point_view // constrainAxis:(BOOL)constrainAxis // { // NSArray *directives = nil; // Point3 modelReferencePoint = ZeroPoint3; // LDrawDrawableElement *firstDirective = nil; // BOOL moved = NO; // // [self publishMouseOverPoint:point_view]; // // if([self->fileBeingDrawn // respondsToSelector:@selector(draggingDirectives)]) // { // directives = [(id)self->fileBeingDrawn draggingDirectives]; // firstDirective = [directives objectAtIndex:0]; // modelReferencePoint = [firstDirective position]; // modelReferencePoint = V3Add(modelReferencePoint, // self->draggingOffset); // // moved = [self updateDirectives:directives // withDragPosition:point_view // depthReferencePoint:modelReferencePoint // constrainAxis:constrainAxis]; // if(moved) // { // if([self->fileBeingDrawn // respondsToSelector:@selector(optimizeVertexes)]) // { // [(id)self->fileBeingDrawn optimizeVertexes]; // } // // [self->fileBeingDrawn noteNeedsDisplay]; // } // } // // }//end updateDirectives:withDragPosition: // // // //========== updateDirectives:withDragPosition: // ================================ // // // // Purpose: Adjusts the directives so they align with the given drag // // location, in window coordinates. // // // //============================================================================== // - (BOOL) updateDirectives:(NSArray *)directives // withDragPosition:(Point2)point_view // depthReferencePoint:(Point3)modelReferencePoint // constrainAxis:(BOOL)constrainAxis // { // LDrawDrawableElement *firstDirective = nil; // Point3 modelPoint = ZeroPoint3; // Point3 oldPosition = ZeroPoint3; // Point3 constrainedPosition = ZeroPoint3; // Vector3 displacement = ZeroPoint3; // Vector3 cumulativeDisplacement = ZeroPoint3; // NSUInteger counter = 0; // BOOL moved = NO; // // firstDirective = [directives objectAtIndex:0]; // // // //---------- Find Location // --------------------------------------------- // // // Where are we? // oldPosition = modelReferencePoint; // // // and adjust. // modelPoint = [self modelPointForPoint:point_view // depthReferencePoint:modelReferencePoint]; // displacement = V3Sub(modelPoint, oldPosition); // cumulativeDisplacement = V3Sub(modelPoint, // self->initialDragLocation); // // // //---------- Find Actual Displacement // ---------------------------------- // // When dragging, we want to move IN grid increments, not move TO // grid // // increments. That means we snap the displacement vector itself to // the // // grid, not part's location. That's because the part may not have // been // // grid-aligned to begin with. // // // As is conventional in graphics programs, we allow dragging to be // // constrained to a single axis. We will pick that axis that is // furthest // // from the initial drag location. // if(constrainAxis == YES) // { // // Find the part's position along the constrained axis. // cumulativeDisplacement = // V3IsolateGreatestComponent(cumulativeDisplacement); // constrainedPosition = V3Add(self->initialDragLocation, // cumulativeDisplacement); // // // Get the displacement from the part's current position to the // // constrained one. // displacement = V3Sub(constrainedPosition, oldPosition); // } // // // Snap the displacement to the grid. // displacement = [firstDirective position:displacement // snappedToGrid:self->gridSpacing]; // // //---------- Update the parts' positions // ------------------------------ // // if(V3EqualPoints(displacement, ZeroPoint3) == NO) // { // // Move all the parts by that amount. // for(counter = 0; counter < [directives count]; counter++) // { // [[directives objectAtIndex:counter] moveBy:displacement]; // } // // moved = YES; // } // // return moved; // // }//end updateDirectives:withDragPosition: // // // #pragma mark - // #pragma mark NOTIFICATIONS // #pragma mark - // ========== activeModelDidChange: // ============================================= // // Purpose: The selected MPD model changed. // // ============================================================================== public void activeModelDidChange() { updateRotationCenter(); camera.setModelSize(fileBeingDrawn.boundingBox3()); if (delegate != null) delegate.LDrawGLRendererNeedsRedisplay(this); }// end displayNeedsUpdating // ========== displayNeedsUpdating: // ============================================= // // Purpose: Someone (likely our file) has notified us that it has // changed, // and thus we need to redraw. // // We also use this opportunity to grow the canvas if necessary. // // ============================================================================== public void displayNeedsUpdating() { camera.setModelSize(fileBeingDrawn.boundingBox3()); if (delegate != null) delegate.LDrawGLRendererNeedsRedisplay(this); }// end displayNeedsUpdating // ========== rotationCenterChanged: // ============================================ // // Purpose: The active model changed the point around which it is to // be spun. // // ============================================================================== public void rotationCenterChanged() { updateRotationCenter(); if (delegate != null) delegate.LDrawGLRendererNeedsRedisplay(this); }// end rotationCenterChanged: // // // #pragma mark - // #pragma mark UTILITIES // #pragma mark - // // ========== getDepthUnderPoint: // =============================================== // // Purpose: Returns the depth component of the nearest object under // the // view // point. // // Returns 1.0 if there is no object under the point. // // ============================================================================== public float getDepthUnderPoint(Vector2f point_view) { Vector2f point_viewport = convertPointToViewport(point_view); Vector2f bl = MatrixMath.V2Make(point_viewport.getX() - HANDLE_SIZE, point_viewport.getY() - HANDLE_SIZE); Vector2f tr = MatrixMath.V2Make(point_viewport.getX() + HANDLE_SIZE, point_viewport.getY() + HANDLE_SIZE); FloatBuffer depth = FloatBuffer.allocate(1); depth.put(0, 1.0f); Box2 viewport = getViewport(); Vector2f point_clip = new Vector2f((point_viewport.getX() - viewport .getOrigin().getX()) * 2.0f / MatrixMath.V2BoxWidth(viewport) - 1.0f, (point_viewport.getY() - viewport.getOrigin().getY()) * 2.0f / MatrixMath.V2BoxHeight(viewport) - 1.0f); float x1 = (float) ((Math.min(bl.getX(), tr.getX()) - viewport .getOrigin().getX()) * 2.0 / MatrixMath.V2BoxWidth(viewport) - 1.0f); float x2 = (float) ((Math.max(bl.getX(), tr.getX()) - viewport .getOrigin().getX()) * 2.0 / MatrixMath.V2BoxWidth(viewport) - 1.0); float y1 = (float) ((Math.min(bl.getY(), tr.getY()) - viewport .getOrigin().getX()) * 2.0 / MatrixMath.V2BoxHeight(viewport) - 1.0); float y2 = (float) ((Math.max(bl.getY(), tr.getY()) - viewport .getOrigin().getY()) * 2.0 / MatrixMath.V2BoxHeight(viewport) - 1.0); Box2 test_box = MatrixMath.V2MakeBox(x1, y1, x2 - x1, y2 - y1); Matrix4 mvp = MatrixMath.Matrix4Multiply( MatrixMath.Matrix4CreateFromGLMatrix4(camera.getModelView()), MatrixMath.Matrix4CreateFromGLMatrix4(camera.getProjection())); ArrayList<LDrawDirective> bestObject = new ArrayList<LDrawDirective>(); fileBeingDrawn.depthTest(point_clip, test_box, mvp, null, bestObject, depth); return depth.get(0) * 0.5f + 0.5f; }// end getDepthUnderPoint // ========== getDirectivesUnderRect:amongDirectives:fastDraw: // ================== // // Purpose: Finds the directives under a given mouse-recangle. This // does a two-pass search so that clients can do a bounding box // test first. // // Parameters: bottom_left, top_right = the rectangle (in viewport // space) // in // which to test. // directives = the directives under consideration for being // clicked. This may be the whole File directive, // or a smaller subset we have already determined // (by a previous call) is in the area. // fastDraw = consider only bounding boxes for hit-detection. // // Returns: Array of all parts that are at least partly inside the // rectangle // in screen space. // // ============================================================================== public ArrayList<LDrawDirective> getDirectivesUnderRect(Box2 rect_view, ArrayList<LDrawDirective> directives, boolean fastDraw) { ArrayList<LDrawDirective> clickedDirectives = null; if (directives.size() == 0) { // If there's nothing to test in, there's no work to do! clickedDirectives = new ArrayList<LDrawDirective>(); } else { Vector2f bottom_left = rect_view.getOrigin(); Vector2f top_right = MatrixMath.V2Make( MatrixMath.V2BoxMaxX(rect_view), MatrixMath.V2BoxMaxY(rect_view)); Vector2f bl = convertPointToViewport(bottom_left); Vector2f tr = convertPointToViewport(top_right); Box2 viewport = getViewport(); TreeSet<LDrawDirective> hits = new TreeSet<LDrawDirective>(); int counter = 0; float x1 = (float) ((Math.min(bl.getX(), tr.getX()) - viewport .getOrigin().getX()) * 2.0 / MatrixMath.V2BoxWidth(viewport) - 1.0); float x2 = (float) ((Math.max(bl.getX(), tr.getX()) - viewport .getOrigin().getX()) * 2.0 / MatrixMath.V2BoxWidth(viewport) - 1.0); float y1 = (float) ((Math.min(bl.getY(), tr.getY()) - viewport .getOrigin().getX()) * 2.0 / MatrixMath.V2BoxHeight(viewport) - 1.0); float y2 = (float) ((Math.max(bl.getY(), tr.getY()) - viewport .getOrigin().getY()) * 2.0 / MatrixMath.V2BoxHeight(viewport) - 1.0); Box2 test_box = MatrixMath.V2MakeBox(x1, y1, x2 - x1, y2 - y1); Matrix4 mvp = MatrixMath.Matrix4Multiply(MatrixMath .Matrix4CreateFromGLMatrix4(camera.getModelView()), MatrixMath.Matrix4CreateFromGLMatrix4(camera .getProjection())); // Do hit test for (counter = 0; counter < directives.size(); counter++) { directives.get(counter).boxTest(test_box, mvp, fastDraw, null, hits); } ArrayList<LDrawDirective> collected = new ArrayList<LDrawDirective>( hits.size()); clickedDirectives = collected; for (LDrawDirective currentDirective : hits) { collected.add(currentDirective); } } return clickedDirectives; }// end getDirectivesUnderMouse:amongDirectives:fastDraw // ========== publishMouseOverPoint: // ============================================ // // Purpose: Informs the delegate that the mouse is hovering over the // model // point under the view point. // // ============================================================================== public void publishMouseOverPoint(Vector2f point_view) { Vector3f modelPoint = Vector3f.getZeroVector3f(); Vector3f modelAxisForX = Vector3f.getZeroVector3f(); // ZeroPoint3; Vector3f modelAxisForY = Vector3f.getZeroVector3f();// ZeroPoint3; Vector3f modelAxisForZ = Vector3f.getZeroVector3f();// ZeroPoint3; Vector3f confidence = Vector3f.getZeroVector3f();// ZeroPoint3; // todo // �좎룞�쇿뜝�뚯뒪 �좎룞�쇿뜝�숈삕 �좎떛釉앹삕���좎룞�쇿뜝�숈삕 �좎룞�쇿뜝�숈삕. �좎룞�쇿뜝�⑹슱���좎뙐�숈삕 // �좎룞�쇿뜝�숈삕�좎뙏�듭삕 �좎떬�먯삕. // if([self->delegate // respondsToSelector:@selector(LDrawGLRenderer:mouseIsOverPoint:confidence:)]) // { // modelPoint = [self modelPointForPoint:point_view]; // // if([self projectionMode] == ProjectionModeOrthographic) // { // [self getModelAxesForViewX:&modelAxisForX Y:&modelAxisForY // Z:&modelAxisForZ]; // // confidence = V3Add(modelAxisForX, modelAxisForY); // } // // [self->delegate LDrawGLRenderer:self mouseIsOverPoint:modelPoint // confidence:confidence]; // } } // ========== setZoomPercentage:preservePoint: // ================================== // // Purpose: Performs cursor-centric zooming on the given point, in // view // coordinates. After the new zoom is applied, the 3D point // projected at viewPoint will still be in the same projected // location. // // ============================================================================== public void setZoomPercentage(float newPercentage, Vector2f viewPoint) { Vector3f modelPoint = modelPointForPoint(viewPoint); camera.setZoomPercentage(newPercentage, modelPoint); }// end setZoomPercentage:preservePoint: // ========== scrollCenterToModelPoint: // ========================================= // // Purpose: Scrolls the receiver (if it is inside a scroll view) so // that // newCenter is at the center of the viewing area. newCenter is // given in LDraw model coordinates. // // ============================================================================== public void scrollCenterToModelPoint(Vector3f modelPoint) { scrollModelPoint(modelPoint, MatrixMath.V2Make(0.5f, 0.5f)); } // ========== scrollModelPoint:toViewportProportionalPoint: // ===================== // // Purpose: Scrolls viewport so the projection of the given 3D point // appears // at the given fraction of the viewport. (0,0) means the // bottom-right corner of the viewport; (0.5, 0.5) means the // center; (1.0, 1.0) means the top-right. // // ============================================================================== public void scrollModelPoint(Vector3f modelPoint, Vector2f viewportPoint) { camera.scrollModelPoint(modelPoint, viewportPoint); }// end scrollCenterToModelPoint: // ========== updateRotationCenter // ============================================== // // Purpose: Resync our copy of the rotationCenter with the one used // by // the // model. // // ============================================================================== public void updateRotationCenter() { Vector3f point = Vector3f.getZeroVector3f(); if (LDrawFile.class.isInstance(fileBeingDrawn)) { point = (((LDrawFile) fileBeingDrawn).activeModel()) .rotationCenter(); } else if (LDrawModel.class.isInstance(fileBeingDrawn)) { point = ((LDrawModel) fileBeingDrawn).rotationCenter(); } camera.setRotationCenter(point); } // ========== convertPointFromViewport: // ========================================= // // Purpose: Converts the point from the viewport coordinate system to // the // view bounds' coordinate system. // // ============================================================================== public Vector2f convertPointFromViewport(Vector2f viewportPoint) { Vector2f point_visibleRect = Vector2f.getZeroVector2f(); Vector2f point_view = Vector2f.getZeroVector2f(); // Rescale to visible rect point_visibleRect.setX(viewportPoint.getX() / (zoomPercentageForGL() / 100.0f)); point_visibleRect.setY(viewportPoint.getY() / (zoomPercentageForGL() / 100.0f)); // The viewport origin is always at (0,0), so wo only need to // translate // if // the coordinate system is flipped. // Flip the coordinates if (isFlipped()) { // The origin of the viewport is in the lower-left corner. // The origin of the view is in the upper right (it is flipped) point_visibleRect.setY(MatrixMath.V2BoxHeight(scroller .getVisibleRect()) - point_visibleRect.getY()); } // Translate to full bounds coordinates point_view.setX(point_visibleRect.getX() + scroller.getVisibleRect().getOrigin().getX()); point_view.setY(point_visibleRect.getY() + scroller.getVisibleRect().getOrigin().getY()); return point_view; }// end convertPointFromViewport: // ========== convertPointToViewport: // =========================================== // // Purpose: Converts the point from the view bounds' coordinate // system // into // the viewport's coordinate system. // // ============================================================================== public Vector2f convertPointToViewport(Vector2f point_view) { Vector2f point_visibleRect = Vector2f.getZeroVector2f(); Vector2f point_viewport = Vector2f.getZeroVector2f(); // Translate from full bounds coordinates to the visible rect point_visibleRect.setX(point_view.getX() - scroller.getVisibleRect().getOrigin().getX()); point_visibleRect.setY(point_view.getY() - scroller.getVisibleRect().getOrigin().getY()); // Flip the coordinates if (isFlipped()) { // The origin of the viewport is in the lower-left corner. // The origin of the view is in the upper right (it is flipped) point_visibleRect.setY(MatrixMath.V2BoxHeight(scroller .getVisibleRect()) - point_visibleRect.getY()); } // Rescale to viewport pixels point_viewport.setX(point_visibleRect.getX() * (zoomPercentageForGL() / 100.0f)); point_viewport.setY(point_visibleRect.getY() * (zoomPercentageForGL() / 100.0f)); return point_viewport; }// end convertPointToViewport: // ========== getModelAxesForViewX:Y:Z: // ========================================= // // Purpose: Finds the axes in the model coordinate system which most // closely // project onto the X, Y, Z axes of the view. // // Notes: The screen coordinate system is right-handed: // // +y // | // | // *-- +x // / // +z // // The choice between what is the "closest" axis in the model is // often arbitrary, but it will always be a unique and // sensible-looking choice. // // ============================================================================== public void getModelAxesForView(Vector3f outModelX, Vector3f outModelY, Vector3f outModelZ) { Vector4f screenX = new Vector4f(1, 0, 0, 0); Vector4f screenY = new Vector4f(0, 1, 0, 0); Vector4f unprojectedX, unprojectedY; // the vectors in the model which // are // projected onto x,y on screen Vector3f modelX, modelY, modelZ; // the closest model axes to which the // screen's x,y,z align // Translate the x, y, and z vectors on the surface of the screen // into // the // axes to which they most closely align in the model itself. // This requires the inverse of the current transformation matrix, so // we // can // convert projection-coordinates back to the model coordinates they // are // displaying. Matrix4 inversed = getInverseMatrix(); // find the vectors in the model which project onto the screen's axes // (We only care about x and y because this is a two-dimensional // projection, and the third axis is consquently ambiguous. See // below.) unprojectedX = MatrixMath.V4MulPointByMatrix(screenX, inversed); unprojectedY = MatrixMath.V4MulPointByMatrix(screenY, inversed); // find the actual axes closest to those model vectors modelX = MatrixMath.V3FromV4(unprojectedX); modelY = MatrixMath.V3FromV4(unprojectedY); modelX = MatrixMath.V3IsolateGreatestComponent(modelX); modelY = MatrixMath.V3IsolateGreatestComponent(modelY); modelX = MatrixMath.V3Normalize(modelX); modelY = MatrixMath.V3Normalize(modelY); // The z-axis is often ambiguous because we are working backwards // from a // two-dimensional screen. Thankfully, while the process used for // deriving // the x and y vectors is perhaps somewhat arbitrary, it always // yields // sensible and unique results. Thus we can simply derive the // z-vector, // which will be whatever axis x and y *didn't* land on. modelZ = MatrixMath.V3Cross(modelX, modelY); if (outModelX != null) outModelX.set(modelX); if (outModelY != null) outModelY.set(modelY); if (outModelZ != null) outModelZ.set(modelZ); }// end getModelAxesForViewX:Y:Z: // ========== modelPointForPoint: // =============================================== // // Purpose: Unprojects the given point (in view coordinates) back // into a // point in the model which projects there, using existing data in // the depth buffer to infer the location on the z axis. // // Notes: The depth buffer is not super-accurate, but it's passably // close. But most importantly, it could be faster to read the // depth buffer than to redraw parts of the model under a pick // matrix. // // ============================================================================== public Vector3f modelPointForPoint(Vector2f viewPoint) { Vector2f viewportPoint = convertPointToViewport(viewPoint); float depth = 0.0f; TransformComponents partTransform = TransformComponents .getIdentityComponents(); Vector3f contextPoint = Vector3f.getZeroVector3f(); Vector3f modelPoint = Vector3f.getZeroVector3f(); depth = getDepthUnderPoint(viewPoint); if (depth == 1.0) { // Error! // Maximum depth readings essentially tell us that no pixels were // drawn // at this point. So we have to make up a best guess now. This guess // will very likely be wrong, but there is little else which can be // done. // todo // �좎룞�쇿뜝�뚯뒪 �좎떛釉앹삕�� window �좎떛釉앹삕���좎떛�몄삕�좎떦�먯삕 �좎떥釉앹삕. // �좎떇�듭삕�좎뙏�쎌삕 java // �좎룞�쇿뜝�숈삕�좎룞�쇿뜝占��좎뙐�숈삕 吏쒎뜝�숈삕 �좎룞�� // if(delegate.respondsToSelector:@selector(LDrawGLRendererPreferredPartTransform:)]) // { // partTransform = [self->delegate // LDrawGLRendererPreferredPartTransform:self]; // } modelPoint = modelPointForPoint(viewPoint, partTransform.getTranslate()); } else { // Convert to 3D viewport coordinates contextPoint = MatrixMath.V3Make(viewportPoint.getX(), viewportPoint.getY(), depth); // Convert back to a point in the model. modelPoint = MatrixMath.V3Unproject(contextPoint, MatrixMath .Matrix4CreateFromGLMatrix4(camera.getModelView()), MatrixMath.Matrix4CreateFromGLMatrix4(camera .getProjection()), getViewport()); } return modelPoint; }// end modelPointForPoint: // ========== modelPointForPoint:depthReferencePoint: // =========================== // // Purpose: Unprojects the given point (in view coordinates) back // into a // point in the model which projects there, calculating the // location on the z axis using the given depth reference point. // // Notes: Any point on the screen represents the projected location // of an // infinite number of model points, extending on a line from the // near to the far clipping plane. // // It's impossible to boil that down to a single point without // being given some known point in the model to determine the // desired depth. (Hence the depthPoint parameter.) The returned // point will lie on a plane which contains depthPoint and is // perpendicular to the model axis most closely aligned to the // computer screen's z-axis. // // * * * * // // When viewing the model with an orthographic projection and the // camera pointing parallel to one of the model's coordinate axes, // this method is useful for determining two of the three // coordinates over which the mouse is hovering. To find which // coordinate is bogus, we call -getModelAxesForViewX:Y:Z:. The // returned z-axis indicates the unreliable point. // // ============================================================================== public Vector3f modelPointForPoint(Vector2f viewPoint, Vector3f depthPoint) { Box2 viewport = getViewport(); Vector2f contextPoint = convertPointToViewport(viewPoint); Vector3f nearModelPoint = Vector3f.getZeroVector3f(); Vector3f farModelPoint = Vector3f.getZeroVector3f(); Vector3f modelPoint = Vector3f.getZeroVector3f(); Vector3f modelZ = Vector3f.getZeroVector3f(); float t = 0.0f; // parametric variable // gluUnProject takes a window "z" coordinate. These values range // from // 0.0 (on the near clipping plane) to 1.0 (the far clipping plane). // - Near clipping plane unprojection nearModelPoint = MatrixMath.V3Unproject(MatrixMath.V3Make( contextPoint.getX(), contextPoint.getY(), 0.0f), MatrixMath .Matrix4CreateFromGLMatrix4(camera.getModelView()), MatrixMath .Matrix4CreateFromGLMatrix4(camera.getProjection()), viewport); // - Far clipping plane unprojection farModelPoint = MatrixMath.V3Unproject(MatrixMath.V3Make( contextPoint.getX(), contextPoint.getY(), 1.0f), MatrixMath .Matrix4CreateFromGLMatrix4(camera.getModelView()), MatrixMath .Matrix4CreateFromGLMatrix4(camera.getProjection()), viewport); // ---------- Derive the actual point from the depth point // -------------- // // We now have two accurate unprojected coordinates: the near (P1) // and // far (P2) points of the line through 3-D space which projects onto // the // single screen point. // // The parametric equation for a line given two points is: // // / \ / // L = | 1 - t | P + t P (see? at t=0, L = P1 and at t=1, L = P2. // \ / 1 2 // // So for example, z = (1-t)*z1 + t*z2 // z = z1 - t*z1 + t*z2 // // / \ / // z = z - t | z - z | // 1 \ 1 2/ // // // z - z // 1 No need to worry about dividing // t = --------- by 0 because the axis we are // z - z inspecting will never be // 1 2 perpendicular to the screen. // Which axis are we going to use from the reference point? // Vector3f temp = Vector3f.getZeroVector3f(); getModelAxesForView((Vector3f) null, (Vector3f) null, modelZ); // Find the value of the parameter at the depth point. if (modelZ.getX() != 0) { t = (nearModelPoint.getX() - depthPoint.getX()) / (nearModelPoint.getX() - farModelPoint.getX()); } else if (modelZ.getY() != 0) { t = (nearModelPoint.getY() - depthPoint.getY()) / (nearModelPoint.getY() - farModelPoint.getY()); } else if (modelZ.getZ() != 0) { t = (nearModelPoint.getZ() - depthPoint.getZ()) / (nearModelPoint.getZ() - farModelPoint.getZ()); } // Evaluate the equation of the near-to-far line at the parameter for // the depth point. modelPoint.setX(MatrixMath.LERP(t, nearModelPoint.getX(), farModelPoint.getX())); modelPoint.setY(MatrixMath.LERP(t, nearModelPoint.getX(), farModelPoint.getY())); modelPoint.setZ(MatrixMath.LERP(t, nearModelPoint.getZ(), farModelPoint.getZ())); return modelPoint; }// end modelPointForPoint:depthReferencePoint: // @Override // public void receiveNotification(NotificationMessageT notificationType, // INotificationMessage msg) { // switch (notificationType) { // case LDrawDirectiveDidChange: // case LDrawFileActiveModelDidChange: // case LDrawModelRotationCenterDidChange: // displayNeedsUpdating(); // break; // default: // break; // } // } public boolean isReadyToUse() { return this.isReadyToUse; } public void setUseWireFrame(boolean b) { this.useWireFrame = b; } }